package game.games.rpg;

import game.gfx.ColoredChar;
import game.gfx.ConsoleColor;
import game.util.Logger;
import game.util.Util;

import java.awt.Dimension;
import java.awt.Point;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import javax.swing.JOptionPane;
import javax.swing.filechooser.FileSystemView;

public class SaveGame
{
    private static final String X_OPEN         = ":[";
    private static final String X_CLOSE        = "]";
    
    private static final String NAME           = "NAME";
    private static final String POSITION       = "POSITION";
    
    private static final String WORLD          = "WORLD";
    private static final String CURRENTROOM    = "CURRENTROOM";
    
    private static final String ROOM           = "ROOM";
    private static final String SIZE           = "SIZE";
    private static final String CHARS          = "CHARS";
    private static final String PLAYERPOSITION = "PLAYERPOSITION";
    private static final String VIEW_RADIUS    = "VIEW_RADIUS";
    private static final String CLEAR_EXPLORED = "CLEAR_EXPLORED";
    private static final String EXPLORED       = "EXPLORED";
    private static final String VISIBLE        = "VISIBLE";
    private static final String NEVER_CLEAR    = "NEVER_CLEAR";
    
    private static final String PLAYER         = "PLAYER";
    private static final String LEVEL          = "LEVEL";
    private static final String LOOKING        = "LOOKING";
    private static final String ATTACK         = "ATTACK";
    private static final String HEALTH         = "HEALTH";
    private static final String MAXHEALTH      = "MAXHEALTH";
    
    private static final String MONSTER        = "MONSTER";
    private static final String BODY           = "BODY";
    private static final String COLOR          = "COLOR";
    
    private static final String DOOR           = "DOOR";
    private static final String OPEN           = "OPEN";
    
    public static File getSaveDir() throws IOException
    {
        File saveDir = new File(FileSystemView.getFileSystemView().getHomeDirectory(), ".rpg_savedir");
        if (saveDir.exists() == false)
        {
            if (saveDir.mkdir())
            {
                Logger.debug("CREATED SAVE-DIR: ", saveDir.getAbsolutePath());
            }
            else
            {
                throw new IOException("Can not create save-dir: " + saveDir.getAbsolutePath());
            }
        }
        if (saveDir.isDirectory() == false)
        {
            throw new IOException("Save-dir is no directory: " + saveDir.getAbsolutePath());
        }
        return saveDir;
    }
    
    private static final String fileSuffix = ".save";
    
    public static File[] getSaveFiles() throws IOException
    {
        return getSaveDir().listFiles(new FilenameFilter()
        {
            @Override
            public boolean accept(File dir, String name)
            {
                return name.endsWith(fileSuffix);
            }
        });
    }
    
    private static void openEntry(BufferedWriter w, String key) throws IOException
    {
        w.append(key).append(X_OPEN);
    }
    
    private static void closeEntry(BufferedWriter w) throws IOException
    {
        w.append(X_CLOSE);
        w.newLine();
    }
    
    private static void addEntry(BufferedWriter w, String key, Object value) throws IOException
    {
        openEntry(w, key);
        w.append(Util.toString(value));
        closeEntry(w);
    }
    
    private static void addEntry(BufferedWriter w, String key, char[] value) throws IOException
    {
        String string = "";
        if (value != null && value.length > 0)
        {
            string = new String(value);
        }
        addEntry(w, key, string);
    }
    
    private final static String split = ";";
    
    private static String toArrayString(Object ... objects)
    {
        if (objects.length == 0)
        {
            return "";
        }
        StringBuilder s = new StringBuilder();
        for (int i = 0; i < objects.length; i++)
        {
            s.append(Util.toString(objects[i]));
            s.append(split);
        }
        s.deleteCharAt(s.length() - 1);
        return s.toString();
    }
    
    private static String[] fromArrayString(String arrayString)
    {
        if (arrayString.isEmpty())
        {
            return new String[0];
        }
        return arrayString.split(Pattern.quote(split));
    }
    
    public static void save(boolean quicksave, World world) throws IOException
    {
        save(quicksave, world, Charset.forName("UTF-8"));
    }
    
    public static void save(boolean quicksave, World world, Charset charset) throws IOException
    {
        File saveFile = null;
        if (quicksave)
        {
            do
            {
                saveFile = new File(getSaveDir(), System.currentTimeMillis() + fileSuffix);
            }
            while (saveFile.exists());
        }
        else
        {
            boolean override = false;
            do
            {
                String fileName = JOptionPane.showInputDialog("Enter Name:");
                if (fileName == null)
                {
                    return;
                }
                if (fileName.endsWith(fileSuffix) == false)
                {
                    fileName += fileSuffix;
                }
                saveFile = new File(getSaveDir(), fileName);
                override = saveFile.exists() && 
                           JOptionPane.showConfirmDialog(null, fileName + " already exists. Override?") == JOptionPane.YES_OPTION;
            }
            while (override == false && saveFile.exists());
            // delete file if override existing savefile
            if (override && saveFile.exists())
            {
                if (saveFile.delete() == false)
                {
                    throw new IOException("Can not delete file: " + saveFile.getAbsolutePath());
                }
            }
        }
        Logger.info((quicksave ? "QUICK" : "") + "SAVE-FILE: ", saveFile.getAbsolutePath());
        // create file
        if (saveFile.createNewFile() == false)
        {
            throw new IOException("can not create save-file: " + saveFile.getAbsolutePath());
        }
        // open Writer
        BufferedWriter w = null;
        try
        {
            w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(saveFile), charset));
            // save world
            openEntry(w, WORLD);
            w.newLine();
            addEntry(w, NAME,        world.getName());
            addEntry(w, CURRENTROOM, world.getCurrentRoom().getName());
            // save player
            openEntry(w, PLAYER);
            w.newLine();
            addEntry(w, NAME,      world.getPlayer().getName());
            addEntry(w, LEVEL,     world.getPlayer().getLevel());
            addEntry(w, LOOKING,   world.getPlayer().getLookingDirection());
            addEntry(w, ATTACK,    world.getPlayer().getAttack());
            addEntry(w, HEALTH,    world.getPlayer().getHealth());
            addEntry(w, MAXHEALTH, world.getPlayer().getMaxHealth());
            closeEntry(w);
            // save rooms
            for (Entry<String, Room> r : world.getRooms().entrySet())
            {
                Room room = r.getValue();
                openEntry(w, ROOM);
                w.newLine();
                addEntry(w,  NAME, r.getKey());
                addEntry(w,  SIZE, room.getSize());
                openEntry(w, CHARS);
                w.newLine();
                for (int y = 0; y < room.getHeight(); y++)
                {
                    for (int x = 0; x < room.getWidth(); x++)
                    {
                        char c = room.get(x, y).getChar();
                        if (room.isMonsterPosition(x, y))
                        {
                            c = Room.MONSTER;
                        }
                        else if (room.isPlayerPosition(x, y))
                        {
                            c = Room.PLAYER;
                        }
                        w.append(c);
                    }
                    w.newLine();
                }
                closeEntry(w);
                addEntry(w, PLAYERPOSITION, world.getLastPlayerPosition(r.getKey()));
                addEntry(w, VIEW_RADIUS,    room.getViewRadius());
                // explored
                addEntry(w, CLEAR_EXPLORED, room.isClearExplored());
                addEntry(w, EXPLORED,       toArrayString(room.getExplored().toArray()));
                addEntry(w, VISIBLE,        toArrayString(room.getVisible().toArray()));
                addEntry(w, NEVER_CLEAR,    room.getNeverClear());
                // save monsters
                for (Entry<Point, Monster> m : room.getMonsters().entrySet())
                {
                    openEntry(w, MONSTER);
                    w.newLine();
                    Monster monster = m.getValue();
                    addEntry(w, NAME,  monster.getName());
                    addEntry(w, LEVEL, monster.getLevel());
                    if (monster instanceof FancyMonster)
                    {
                        FancyMonster fancymonster = (FancyMonster) monster;
                        StringBuilder bodys = new StringBuilder();
                        List<ConsoleColor> colors = new ArrayList<ConsoleColor>(fancymonster.getBodys().length);
                        for (ColoredChar c : fancymonster.getBodys())
                        {
                            bodys.append(c.getChar());
                            colors.add(c.getConsoleColor());
                        }
                        
                        addEntry(w, BODY,  bodys.toString());
                        addEntry(w, COLOR, toArrayString(colors.toArray()));
                    }
                    else
                    {
                        addEntry(w, BODY,     monster.getChar().getChar());
                        addEntry(w, COLOR,    monster.getChar().getConsoleColor());
                    }
                    addEntry(w, POSITION,  m.getKey());
                    addEntry(w, LOOKING,   monster.getLookingDirection());
                    addEntry(w, ATTACK,    monster.getAttack());
                    addEntry(w, HEALTH,    monster.getHealth());
                    addEntry(w, MAXHEALTH, monster.getMaxHealth());
                    closeEntry(w);
                }
                // save doors
                for (Entry<Point, Door> d : room.getDoors().entrySet())
                {
                    openEntry(w, DOOR);
                    w.newLine();
                    addEntry(w, POSITION, d.getKey());
                    addEntry(w, OPEN,     d.getValue().isOpen());
                    closeEntry(w);
                }
                closeEntry(w);
            }
        }
        catch (IOException ex)
        {
            Logger.fatal("save file corrupt: ", saveFile.getAbsolutePath(), ex);
            throw ex;
        }
        finally
        {
            if (w != null)
            {
                w.close();
            }
        }
        Logger.info("ROOM SAVED: ", saveFile.getAbsolutePath());
    }
    
    public static World load(File saveFile) throws IOException
    {
        return load(saveFile, Charset.forName("UTF-8"));
    }
    
    public static World load(File saveFile, Charset charset) throws IOException
    {
        BufferedReader r = null;
        try
        {
            World worldBuffer = null;
            Player playerBuffer = null;
            Room roomBuffer = null;
            StringBuilder stringBuffer = null;
            Monster monsterBuffer = null;
            Door doorBuffer = null;
            Object buffer = null;
            List<String> stack = new ArrayList<String>();
            // reader
            r = new BufferedReader(new InputStreamReader(new FileInputStream(saveFile), charset));
            String line = null;
            while ((line = r.readLine()) != null)
            {
                Logger.trace("READ-LINE: ", line);
                int open  = line.indexOf(X_OPEN);
                int close = line.indexOf(X_CLOSE);
                if (open == -1)
                {
                    if (X_CLOSE.equals(line))
                    {
                        String current = stack.get(stack.size() - 1);
                        if (WORLD.equals(current))
                        {
                            current = null;
                        }
                        else if (PLAYER.equals(current))
                        {
                            worldBuffer.setPlayer(playerBuffer);
                            playerBuffer = null;
                        }
                        else if (ROOM.equals(current))
                        {
                            worldBuffer.addRoom(roomBuffer);
                            roomBuffer = null;
                        }
                        else if (MONSTER.equals(current))
                        {
                            if (buffer instanceof Point)
                            {
                                Point p = (Point) buffer;
                                roomBuffer.addMonster(p.x, p.y, monsterBuffer);
                            }
                            else
                            {
                                Logger.showError("X_CLOSE MONSTER with buffer: " + (buffer == null ? "NULL" : buffer.getClass()));
                            }
                            monsterBuffer = null;
                            buffer = null;
                        }
                        else if (DOOR.equals(current))
                        {
                            if (buffer instanceof Point)
                            {
                                Point p = (Point) buffer;
                                roomBuffer.addDoor(p.x, p.y, doorBuffer);
                            }
                            else
                            {
                                Logger.showError("X_CLOSE DOOR with buffer: " + (buffer == null ? "NULL" : buffer.getClass()));
                            }
                            doorBuffer = null;
                            buffer = null;
                        }
                        else if (CHARS.equals(current))
                        {
                            roomBuffer.setChars(stringBuffer.toString());
                            stringBuffer = null;
                        }
                        else
                        {
                            Logger.showError("X_CLOSE?: " + current);
                        }
                        stack.remove(stack.size() - 1);
                    }
                    else
                    {
                        if (stringBuffer == null)
                        {
                            Logger.showError("stringBuffer is null!");
                        }
                        stringBuffer.append(line);
                    }
                }
                else
                {
                    String key = line.substring(0, open);
                    if (close == -1)
                    {
                        String current = key;
                        if (WORLD.equals(current))
                        {
                            worldBuffer = new World();
                        }
                        else if (PLAYER.equals(current))
                        {
                            playerBuffer = new Player();
                        }
                        else if (ROOM.equals(current))
                        {
                            roomBuffer = new Room();
                        }
                        else if (CHARS.equals(current))
                        {
                            stringBuffer = new StringBuilder();
                        }
                        else if (MONSTER.equals(current))
                        {
                            monsterBuffer = new SimpleMonster();
                        }
                        else if (DOOR.equals(current))
                        {
                            doorBuffer = new Door();
                        }
                        else
                        {
                            Logger.showError("UNKNOW KEY: " + key);
                        }
                        stack.add(current);
                    }
                    // multiline entry?
                    else
                    {
                        String current = stack.get(stack.size() - 1);
                        String value = line.substring(open + X_OPEN.length(), close);
                        // WORLD
                        if (WORLD.equals(current))
                        {
                            if (NAME.equals(key))
                            {
                                worldBuffer.setName(value);
                            }
                            else if (CURRENTROOM.equals(key))
                            {
                                worldBuffer.setCurrentRoom(value);
                            }
                        }
                        // PLAYER
                        else if (PLAYER.equals(current))
                        {
                            if (NAME.equals(key))
                            {
                                playerBuffer.setName(value);
                            }
                            else if (LEVEL.equals(key))
                            {
                                playerBuffer.setLevel(Integer.parseInt(value));
                            }
                            else if (LOOKING.equals(key))
                            {
                                playerBuffer.setLookingDirection(Util.toObject(value, Direction.class));
                            }
                            else if (ATTACK.equals(key))
                            {
                                playerBuffer.setAttack(Integer.parseInt(value));
                            }
                            else if (HEALTH.equals(key))
                            {
                                playerBuffer.setHealth(Integer.parseInt(value));
                            }
                            else if (MAXHEALTH.equals(key))
                            {
                                playerBuffer.setMaxHealth(Integer.parseInt(value));
                            }
                            else if (BODY.equals(key))
                            {
                                playerBuffer.getChar().setChar(value.charAt(0));
                            }
                            else if (COLOR.equals(key))
                            {
                                playerBuffer.getChar().setConsoleColor(Util.toObject(value, ConsoleColor.class));
                            }
                        }
                        // ROOM
                        else if (ROOM.equals(current))
                        {
                            if (NAME.equals(key))
                            {
                                roomBuffer.setName(value);
                            }
                            else if (SIZE.equals(key))
                            {
                                roomBuffer.init(Util.toObject(value, Dimension.class));
                            }
                            else if (PLAYERPOSITION.equals(key))
                            {
                                roomBuffer.setPlayerPosition(Util.toObject(value, Point.class));
                            }
                            else if (VIEW_RADIUS.equals(key))
                            {
                                roomBuffer.setViewRadius(Integer.parseInt(value));
                            }
                            else if (CLEAR_EXPLORED.equals(key))
                            {
                                roomBuffer.setClearExplored(Boolean.parseBoolean(value));
                            }
                            else if (EXPLORED.equals(key))
                            {
                                for (String string : fromArrayString(value))
                                {
                                    roomBuffer.setExplored(Util.toObject(string, Point.class));
                                }
                            }
                            else if (VISIBLE.equals(key))
                            {
                                for (String string : fromArrayString(value))
                                {
                                    roomBuffer.addVisible(Util.toObject(string, Point.class));
                                }
                            }
                            else if (NEVER_CLEAR.equals(key))
                            {
                                roomBuffer.setNeverClear(value.toCharArray());
                            }
                        }
                        // MONSTER
                        else if (MONSTER.equals(current))
                        {
                            if (NAME.equals(key))
                            {
                                if (value.length() > 0 && monsterBuffer instanceof SimpleMonster == false)
                                {
                                    monsterBuffer = new SimpleMonster(value, monsterBuffer.getChar().getConsoleColor(), monsterBuffer.getLevel(), monsterBuffer.getHealth(), monsterBuffer.getAttack());
                                }
                                monsterBuffer.setName(value);
                            }
                            else if (LEVEL.equals(key))
                            {
                                monsterBuffer.setLevel(Integer.parseInt(value));
                            }
                            else if (ATTACK.equals(key))
                            {
                                monsterBuffer.setAttack(Integer.parseInt(value));
                            }
                            else if (HEALTH.equals(key))
                            {
                                monsterBuffer.setHealth(Integer.parseInt(value));
                            }
                            else if (MAXHEALTH.equals(key))
                            {
                                monsterBuffer.setMaxHealth(Integer.parseInt(value));
                            }
                            else if (POSITION.equals(key)) 
                            {
                                buffer = Util.toObject(value, Point.class);
                            }
                            else if (LOOKING.equals(key))
                            {
                                monsterBuffer.setLookingDirection(Util.toObject(value, Direction.class));
                            }
                            else if (COLOR.equals(key))
                            {
                                if (value.length() > 0)
                                {
                                    if (value.contains(split) == false)
                                    {
                                        monsterBuffer.getChar().setConsoleColor(Util.toObject(value, ConsoleColor.class));
                                    }
                                    else
                                    {
                                        if (monsterBuffer instanceof FancyMonster == false)
                                        {
                                            monsterBuffer = new FancyMonster(null, monsterBuffer.getHealth(), monsterBuffer.getAttack(), false);
                                        }
                                        ColoredChar[] bodys = ((FancyMonster) monsterBuffer).getBodys();
                                        String[] values = fromArrayString(value);
                                        if (bodys.length != values.length)
                                        {
                                            bodys = new ColoredChar[values.length];
                                        }
                                        for (int i = 0; i < values.length; i++)
                                        {
                                            bodys[i] = new ColoredChar('?', Util.toObject(values[i], ConsoleColor.class));
                                        }
                                    }
                                }
                            }
                            else if (BODY.equals(key))
                            {
                                if (value.length() > 0)
                                {
                                    if (value.length() == 1)
                                    {
                                        monsterBuffer.getChar().setChar(value.charAt(0));
                                    }
                                    else
                                    {
                                        if (monsterBuffer instanceof FancyMonster == false)
                                        {
                                            monsterBuffer = new FancyMonster(null, monsterBuffer.getHealth(), monsterBuffer.getAttack(), false);
                                        }
                                        ColoredChar[] bodys = ((FancyMonster) monsterBuffer).getBodys();
                                        if (bodys.length != value.length())
                                        {
                                            Logger.fatal("WTF ??? " + bodys.length + " != " + value.length());
                                        }
                                        for (int i = 0; i < value.length(); i++)
                                        {
                                            bodys[i].setChar(value.charAt(i));
                                        }
                                    }
                                }
                            }
                        }
                        // DOOR
                        else if (DOOR.equals(current))
                        {
                            if (POSITION.equals(key))
                            {
                                buffer = Util.toObject(value, Point.class);
                            }
                            else if (OPEN.equals(key))
                            {
                                doorBuffer.setOpen(Boolean.parseBoolean(value));
                            }
                        }
                        else
                        {
                            Logger.showError("UNKNOW STACK-POSITION: " + current);
                        }
                    }
                }
            }
            return worldBuffer;
        }
        catch (IOException ex)
        {
            Logger.fatal("save file corrupt: ", saveFile.getAbsolutePath(), ex);
            throw ex;
        }
        finally
        {
            if (r != null)
            {
                r.close();
            }
        }
    }
}
